private 상속은 is-a 관계를 뜻하지 않는다.
상속 관계가 private이면, 컴파일러는 일반적으로 파생 클래스 객체를 기본 클래스 객체로 변환하지 않는다
(암시적 변환 X)
기본 클래스의 모든 멤버가 파생 클래스에서 private에 정의된다.
class Person{ };
class Student: private Person{ };
void eat(const Person& p);
void student(const Student& s);
Person p;
Student s;
eat(p);
eat(s);
private 상속은 is-implemented-in-terms-of의 의미를 가진다.
private 상속은 인터페이스를 모두 private으로 숨기며,
내부적으로 구현에만 이를 사용할 수 있다.
소프트웨어 설계(design)에서는 의미를 가지지 않으며, 소프트웨어 구현 중에만 의미를 가짐
객체 합성(Object Composition)과 private 상속 모두 is-implemented-in-terms-of의 의미를 가진다.
공간 문제가 얽혀 private 상속을 쓸 수 밖에 없는 경우가 아니면, 객체 합성으로 이를 구현할 것
private 상속으로 is-implemented-in-terms-of 구현클래스의 일부 구현을 사용하기 위해 private으로 클래스를 상속
class Timer{
public:
explicit Timer(int tickFrequency);
virtual void onTick() const;
};
class Widget: private Timer{
private:
virtual void onTick() const;
};
객체 합성으로 is-implemented-in-terms-of 구현
class Widget{
private:
class WidgetTimer: public Timer{
public:
virtual void onTick() const;
};
WidgetTimer timer;
};
객체 합성을 사용하는 것이 더 좋은 이유1. 파생은 가능하게 하되, 파생 클래스에서 가상함수를 재정의 할 수 없도록 설계할 수 있다.
->private으로 상속해도, 함수에 대해서 재정의가 가능하다.(Java의 final 메서드와 유사)
2. 컴파일 의존성을 최소화할 수 있다.
->위의 코드에서 WidgetTimer의 정의를 Widget 밖에서 하고, Widget이 WidgetTimer의 포인터를 가진다면,
컴파일 의존성을 최소화할 수 있다.
어쩔 수 없이 private 상속을 사용해야 하는 경우,C++은 공백 클래스(empty class)에 대해 char(일반적인 컴파일러)를 넣어서 size를 1바이트로 만들어 사용한다.
(C++에는 독립 구조(freestanding)의 객체는 반드시 크기가 0을 넘어야 한다는 규칙이 있다)
class Empty{};
class HoldsAnInt{
private:
int x;
Empty e;
};
따라서 위에서 'sizeof(HoldsAnInt) > sizeof(int)' 가 성립한다.
class HoldsAnInt: private Empty{
private:
int x;
};
하지만, 독립구조가 아닌 경우(단일 상속하에서만 적용됨),
위와 같이 'sizeof(HoldsAnInt) == sizeof(int)’ 가 성립하게 된다.
위와 같은 기법은 공백 기본 클래스 최적화(empty base optimization: EBO)라고 한다.
실은 공백 클래스는 내부적으로
typedef 혹은 enum, 정적 데이터 멤버, 비가상 함수(가상함수는 포인터 vptr을 가짐)를 갖는다.
파생 클래스에서 기본 클래스의 protected 멤버에 접근해야 할 경우,
혹은 상속받은 가상 함수를 재정의해야 하는 경우, private가 적합하다.
(위도 객체 합성으로 커버가 가능하기는 함)